package com.linsen.core.common.aspect;

import cn.hutool.core.net.NetUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON;
import com.linsen.core.common.exception.RequestBusinessException;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.MethodParameter;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.annotation.SynthesizingMethodParameter;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.*;

/**
 * @author zhaoyong
 * @date 2023/03/01
 */
@Slf4j
public abstract class BaseAspect {
    private static final ParameterNameDiscoverer PARAMETER_NAME_DISCOVERER = new DefaultParameterNameDiscoverer();

    private final static ExpressionParser SPEL_EXPRESSION_PARSER = new SpelExpressionParser();

    private final static EvaluationContext STANDARD_EVALUATION_CONTEXT = new StandardEvaluationContext();

    private final String[] HEADERS = new String[]{"X-Forwarded-For", "X-Real-IP", "Proxy-Client-IP", "WL-Proxy-Client-IP", "HTTP_CLIENT_IP", "HTTP_X_FORWARDED_FOR"};

    /**
     * 获取上下文中的Request对象
     *
     * @return
     */
    protected HttpServletRequest getRequest() {
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        if (ObjectUtil.isNull(requestAttributes)) {
            throw new RequestBusinessException("非Web上下文无法获取Request");
        }

        return ((ServletRequestAttributes) requestAttributes).getRequest();
    }

    /**
     * 获取上下文中的Response对象
     *
     * @return
     */
    protected HttpServletResponse getResponse() {
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        if (ObjectUtil.isNull(requestAttributes)) {
            throw new RequestBusinessException("非Web上下文无法获取Response");
        }

        return ((ServletRequestAttributes) requestAttributes).getResponse();
    }

    /**
     * 获取方法级别上的注解对象
     *
     * @param point
     * @param annotationClass
     * @param <T>
     * @return
     */
    protected <T extends Annotation> T getAnnotation(JoinPoint point, Class<T> annotationClass) {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        return method.getAnnotation(annotationClass);
    }

    /**
     * @param point
     * @return
     */
    protected Method getMethod(JoinPoint point) {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Class<?> targetClass = point.getTarget().getClass();

        Method method = getDeclaredMethod(targetClass, signature.getName(),
                signature.getMethod().getParameterTypes());
        if (method == null) {
            throw new IllegalStateException("无法解析目标方法: " + signature.getMethod().getName());
        }

        return method;
    }

    /**
     * @param clazz
     * @param name
     * @param parameterTypes
     * @return
     */
    protected Method getDeclaredMethod(Class<?> clazz, String name, Class<?>... parameterTypes) {
        try {
            return clazz.getDeclaredMethod(name, parameterTypes);
        } catch (NoSuchMethodException e) {
            Class<?> superClass = clazz.getSuperclass();
            if (superClass != null) {
                return getDeclaredMethod(superClass, name, parameterTypes);
            }
        }

        return null;
    }

    /**
     * 获取方法参数信息
     *
     * @param method         方法
     * @param parameterIndex 参数序号
     * @return {MethodParameter}
     */
    protected MethodParameter getMethodParameter(Method method, int parameterIndex) {
        MethodParameter methodParameter = new SynthesizingMethodParameter(method, parameterIndex);
        methodParameter.initParameterNameDiscovery(PARAMETER_NAME_DISCOVERER);
        return methodParameter;
    }

    /**
     * 获取客户端IP地址
     *
     * @return
     */
    protected String getClientIP() {
        return this.getClientIP(this.getRequest());
    }

    /**
     * 获取客户端IP地址
     *
     * @param request
     * @return
     */
    protected String getClientIP(HttpServletRequest request) {
        String ip;
        for (String header : HEADERS) {
            ip = request.getHeader(header);
            if (!NetUtil.isUnknown(ip)) {
                return NetUtil.getMultistageReverseProxyIp(ip);
            }
        }

        ip = request.getRemoteAddr();
        return NetUtil.getMultistageReverseProxyIp(ip);
    }

    /**
     * 获取请求参数
     *
     * @param args
     * @param request
     * @return
     */
    protected String getArgs(Object[] args, HttpServletRequest request) {
        List<Object> argList = new LinkedList<>();
        for (Object arg : args) {
            if (arg instanceof HttpServletRequest){
                continue;
            }else if (arg instanceof HttpServletResponse){
                continue;
            }else if (arg instanceof MultipartFile){
                continue;
            }else if (arg instanceof MultipartFile[]){
                continue;
            }else{
                argList.add(arg);
            }
        }

        return JSON.toJSONString(argList);
    }

    /**
     * 获取查询参数
     *
     * @param request
     * @return
     */
    protected String getRequestParams(HttpServletRequest request) {
        Map<String, String> params = new LinkedHashMap<>();
        Enumeration<String> parameterNames = request.getParameterNames();
        while (parameterNames.hasMoreElements()) {
            String parameterName = parameterNames.nextElement();
            params.put(parameterName, request.getParameter(parameterName));
        }

        return JSON.toJSONString(params);
    }

    /**
     * 获取请求头
     *
     * @param request
     * @return
     */
    protected String getRequestHeaders(HttpServletRequest request) {
        Map<String, String> headers = new LinkedHashMap<>();
        Enumeration<String> headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String headerName = headerNames.nextElement();
            headers.put(headerName, request.getHeader(headerName));
        }

        return JSON.toJSONString(headers);
    }

    /**
     * 获取响应头
     *
     * @param response
     * @return
     */
    protected String getResponseHeaders(HttpServletResponse response) {
        Map<String, String> headers = new LinkedHashMap<>();
        Collection<String> headerNames = response.getHeaderNames();
        for (String headerName : headerNames) {
            headers.put(headerName, response.getHeader(headerName));
        }

        return JSON.toJSONString(headers);
    }

    protected Object getSpElValue(JoinPoint joinPoint, String key) {
        if (!StrUtil.contains(key, "#")) {
            return key;
        }

        Method method = this.getMethod(joinPoint);
        String[] parameterNames = PARAMETER_NAME_DISCOVERER.getParameterNames(method);
        if (ArrayUtil.isEmpty(parameterNames)) {
            return null;
        }

        Object[] args = joinPoint.getArgs();
        for (int len = 0; len < parameterNames.length; len++) {
            STANDARD_EVALUATION_CONTEXT.setVariable(parameterNames[len], args[len]);
        }

        Expression expression = SPEL_EXPRESSION_PARSER.parseExpression(key);
        return expression.getValue(STANDARD_EVALUATION_CONTEXT);
    }
}
